// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_ft_font.h"
#include "mk_engine.h"
#include "mk_error.h"
#include "mk_file_system.h"
#include "mk_resource_manager.h"
#include "mk_painter.h"


namespace MK3DE
{
	FT_Library CFTFont::_library = NULL;
	wchar_t CFTFont::_errorChar = '?';

	BOOL CFTFont::Create()
	{
		FT_Error error;
		error = FT_Init_FreeType(&CFTFont::_library);
		if (error)
		{
			GetEngine()->GetLog()->Write(MKLT_ERROR, "Initialize FreeType failed.");
			return FALSE;
		}

		return TRUE;
	}

	void CFTFont::Destroy()
	{
		if (CFTFont::_library)
		{
			FT_Done_FreeType(CFTFont::_library);
			CFTFont::_library = NULL;
		}
	}

	CFTFont::CFTFont(const char* fileName, BOOL bAntialiased, DWORD points) :
	CResource(fileName), 
		_ftFace(NULL),
		_bAntialiased(bAntialiased), 
		_points(points), 
		_borderType(FBT_NONE),
		_borderWeight(0),
		_borderColor(0xffffffff),
		_enableClip(FALSE),
		_clipLeft(0),
		_clipTop(0),
		_clipRight(0),
		_clipBottom(0)
	{
		memset(_wordLocate, 0xff, sizeof(_wordLocate));
		memset(_wordDimension, 0xff, sizeof(_wordDimension));
	}

	CFTFont::~CFTFont()
	{
		std::vector<CTexture2D*>::iterator i = _vecFontTexture.begin();
		while (i != _vecFontTexture.end())
		{
			CTexture2D* texture = *i;
			SAFE_RELEASE(texture);
			++i;
		}
	}

	BOOL CFTFont::Load()
	{
		if (_ftFace)
		{
			FT_Done_Face(_ftFace);
			_ftFace = NULL;
		}

		FT_Error error;
		BOOL ret = GetEngine()->GetFileSystem()->ReadFile(_fileName.c_str(), &_block);
		MK_PROCESS_ERROR(ret);

		error = FT_New_Memory_Face(_library, _block.GetBuffer(), _block.GetSize(), 0, &_ftFace);
		if (error == FT_Err_Unknown_File_Format)
		{
			GetEngine()->GetLog()->Write(MKLT_ERROR, "Bad font format.");
			return FALSE;
		}
		else if (error)
		{
			GetEngine()->GetLog()->Write(MKLT_ERROR, "Bad font file.");
			return FALSE;
		}

		error = FT_Set_Char_Size(_ftFace, 0, _points * 64, 72, 72);
		if (error)
		{
			GetEngine()->GetLog()->Write(MKLT_ERROR, "FT_Set_Char_Size failed.");
			return FALSE;
		}

		return TRUE;
Exit0:
		return FALSE;
	}

	HRESULT CFTFont::LoadGlyph(wchar_t c)
	{
		FT_UInt  glyph_index;

		glyph_index = FT_Get_Char_Index(_ftFace, c);
		if (glyph_index == 0)
			return E_HANDLE;

		FT_Error error = FT_Load_Glyph(_ftFace, glyph_index, FT_LOAD_DEFAULT);
		if (error)
		{
			GetEngine()->GetLog()->Write(MKLT_ERROR, "Load glyph failed!");
			return E_FAIL;
		}

		return S_OK;
	}

	int CFTFont::CheckFontTexture(unsigned long uWidth, unsigned long uHeight, _TextureUsedDesc& usedDesc)
	{
		ASSERT(_vecFontTexture.size() == _vecTextureUsedDesc.size());
		for (int i = 0; i < (int)_vecFontTexture.size(); i++)
		{
			CTexture2D* pTexture = _vecFontTexture[i];
			_TextureUsedDesc &used = _vecTextureUsedDesc[i];
			if (used.uUsedHeightMin + uHeight > pTexture->GetHeight())
				continue;

			if (used.uUsedWidth + uWidth <= pTexture->GetWidth())
			{
				usedDesc.uUsedWidth = used.uUsedWidth + uWidth;
				usedDesc.uUsedHeightMin = used.uUsedHeightMin;
				ASSERT(used.uUsedHeightMax >= used.uUsedHeightMin);
				if (used.uUsedHeightMax - used.uUsedHeightMin < uHeight)
					usedDesc.uUsedHeightMax = used.uUsedHeightMin + uHeight;
				else
					usedDesc.uUsedHeightMax = used.uUsedHeightMax;
				return i;
			}

			if (used.uUsedHeightMax + uHeight > pTexture->GetHeight())
				continue;

			if (uWidth <= pTexture->GetWidth())
			{
				usedDesc.uUsedWidth = uWidth;
				usedDesc.uUsedHeightMax = used.uUsedHeightMax + uHeight;
				usedDesc.uUsedHeightMin = used.uUsedHeightMax;
				return i;
			}
		}

		CTexture2D* pTexture = NULL;
		DWORD uSize = SelectTextureSize();
		if (!GetEngine()->GetResourceManager()->GetTexture2D(uSize, uSize, PF_A8R8G8B8, 1, TU_NORMAL, pTexture))
			return -1;

		usedDesc.uUsedWidth = uWidth;
		usedDesc.uUsedHeightMin = 0;
		usedDesc.uUsedHeightMax = uHeight;
		_vecTextureUsedDesc.push_back(usedDesc);
		_vecFontTexture.push_back(pTexture);
		return (int)(_vecFontTexture.size() - 1);
	}

	DWORD CFTFont::SelectTextureSize()
	{
		if (_points <= 18)
			return 256;
		else if (_points <= 36)
			return 512;
		else
			return 1024;
	}

	HRESULT CFTFont::PaintTexture(wchar_t c)
	{
		HRESULT hr = LoadGlyph(c);
		MK_PROCESS_COM_ERROR(hr);

		FT_Error error;
		if (_bAntialiased)
			error = FT_Render_Glyph(_ftFace->glyph, FT_RENDER_MODE_NORMAL);
		else
			error = FT_Render_Glyph(_ftFace->glyph, FT_RENDER_MODE_MONO);

		MK_PROCESS_ERROR(!error || _ftFace->glyph->format != FT_GLYPH_FORMAT_BITMAP);

		_WordDimension &dimension = _wordDimension[c];
		dimension.uWidth = (unsigned short)_ftFace->glyph->bitmap.width;
		dimension.uHeight = (unsigned short)_ftFace->glyph->bitmap.rows;

		dimension.OffsetX = (short)_ftFace->glyph->bitmap_left;
		dimension.OffsetY = (short)((_ftFace->size->metrics.ascender / 64) - _ftFace->glyph->bitmap_top);

		dimension.uAdvanceX = (unsigned short)_ftFace->glyph->advance.x / 64;

		_TextureUsedDesc usedDesc;
		int texIdx = CheckFontTexture((unsigned long)dimension.uWidth, (unsigned long)dimension.uHeight, usedDesc);
		MK_PROCESS_ERROR(texIdx != -1);

		unsigned short uX = (unsigned short)usedDesc.uUsedWidth - dimension.uWidth;
		unsigned short uY = (unsigned short)usedDesc.uUsedHeightMin;

		if (dimension.uWidth == 0 || dimension.uHeight == 0)
		{
			_vecTextureUsedDesc[texIdx] = usedDesc;
			_wordLocate[c].uTextureIdx = (unsigned short)texIdx;
			_wordLocate[c].uX = uX;
			_wordLocate[c].uY = uY;

			return S_OK;
		}

		CTexture2D* pTexture = _vecFontTexture[texIdx];
		RECT updateRect;
		updateRect.left = uX;
		updateRect.top = uY;
		updateRect.right = uX + dimension.uWidth;
		updateRect.bottom = uY + dimension.uHeight;
		void* pData = NULL;
		DWORD pitch = 0;
		BOOL ret = pTexture->LockRect((void**)&pData, &pitch, &updateRect);
		MK_PROCESS_ERROR(ret);

		if ( ( ( 0 == _ftFace->glyph->outline.n_contours )
			&& ( 0 == _ftFace->glyph->outline.n_points ) )
			|| ( FT_PIXEL_MODE_MONO == _ftFace->glyph->bitmap.pixel_mode ) )
		{
			WriteTextureA8R8G8B8Mono(dimension.uWidth, dimension.uHeight, pData, pitch);
		}
		else
		{
			WriteTextureA8R8G8B8Gray8Bit(dimension.uWidth, dimension.uHeight, pData, pitch);
		}

		ret = pTexture->Unlock();
		MK_PROCESS_ERROR(ret);

		_vecTextureUsedDesc[texIdx] = usedDesc;
		_wordLocate[c].uTextureIdx = (unsigned short)texIdx;
		_wordLocate[c].uX = uX;
		_wordLocate[c].uY = uY;

		return S_OK;
Exit0:
		return hr;
	}

	void CFTFont::WriteTextureA8R8G8B8Gray8Bit(unsigned short uWidth, unsigned short uHeight, void* pData, DWORD pitch)
	{
		for (int y = 0; y < (int)uHeight; y++)
		{
			unsigned long* pDstBuf = (unsigned long*)((unsigned char*)pData + pitch * y);
			unsigned char* pSrcBuf = _ftFace->glyph->bitmap.buffer + _ftFace->glyph->bitmap.pitch * y;

			for (int x = 0; x < (int)uWidth; x++)
			{
				unsigned long c = (unsigned long)*pSrcBuf++;
				*pDstBuf++ = D3DCOLOR_ARGB(c, c, c, c);
			}
		}
	}

	void CFTFont::WriteTextureA8R8G8B8Mono(unsigned short uWidth, unsigned short uHeight, void* pData, DWORD pitch)
	{
		int nPixOffset = 0;
		int nPixRShift = 0;

		for (int y = 0; y < (int)uHeight; y++)
		{
			unsigned long* pDstBuf = (unsigned long*)((unsigned char*)pData + pitch * y);
			unsigned char* pSrcBuf = _ftFace->glyph->bitmap.buffer + _ftFace->glyph->bitmap.pitch * y;

			for (int x = 0; x < (int)uWidth; x += nPixOffset, ++pSrcBuf)
			{
				nPixOffset = min(uWidth - x, 8);
				nPixRShift = 7;

				for (int k = nPixOffset - 1; k >= 0; --k, --nPixRShift)
				{
					unsigned long c =  (((*pSrcBuf) >> nPixRShift) & 1) * 255;
					*pDstBuf++ = D3DCOLOR_ARGB(c, c, c, c);
				}
			}
		}
	}

	void CFTFont::DrawChar(wchar_t c, int x, int y, DWORD dwColor, int& advanceX)
	{
		CPainter* painter = GetEngine()->GetPainter();

		_WordLocate *pLocate = _wordLocate + c;
		if (pLocate->uTextureIdx == 0xffff)
			return;

		_WordDimension *pDimension = _wordDimension + c;
		ASSERT(pDimension->OffsetX != 0xffff);
		ASSERT(pDimension->OffsetY != 0xffff);
		ASSERT(pDimension->uWidth != 0xffff);
		ASSERT(pDimension->uHeight != 0xffff);
		ASSERT(pDimension->uAdvanceX != 0xffff);

		D3DXVECTOR2 leftTopTex(pLocate->uX, pLocate->uY);
		D3DXVECTOR2 rightBottomTex((float)(pLocate->uX + pDimension->uWidth), (float)(pLocate->uY + pDimension->uHeight));

		D3DXVECTOR2 leftTop((float)(x + pDimension->OffsetX), (float)(y + pDimension->OffsetY));
		D3DXVECTOR2 rightBottom((float)(x + pDimension->OffsetX + pDimension->uWidth), (float)(y + pDimension->OffsetY + pDimension->uHeight));

		if (_enableClip)
		{
			if (leftTop.x > _clipRight || 
				rightBottom.x < _clipLeft ||
				leftTop.y > _clipBottom ||
				rightBottom.y < _clipTop)
			{
				advanceX = pDimension->uAdvanceX;
				return;
			}

			if (leftTop.x < _clipLeft)
			{
				float dx = _clipLeft - leftTop.x;
				leftTop.x = _clipLeft;
				leftTopTex.x += dx;
			}

			if (rightBottom.x > _clipRight)
			{
				float dx = rightBottom.x - _clipRight;
				rightBottom.x = _clipRight;
				rightBottomTex.x -= dx;
			}

			if (leftTop.y < _clipTop)
			{
				float dy = _clipTop - leftTop.y;
				leftTop.y = _clipTop;
				leftTopTex.y += dy;
			}

			if (rightBottom.y > _clipBottom)
			{
				float dy = rightBottom.y - _clipBottom;
				rightBottom.y = _clipBottom;
				rightBottomTex.y -= dy;
			}
		}

		CTexture2D* ptexture = _vecFontTexture[pLocate->uTextureIdx];
		painter->DrawImageSub2D(Rect2D(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y),
			Rect2D(leftTopTex.x, leftTopTex.y, rightBottomTex.x, rightBottomTex.y),
			RectColor(dwColor, dwColor, dwColor, dwColor),
			ptexture);

		advanceX = pDimension->uAdvanceX;
	}

	BOOL CFTFont::DrawText(int x, int y, DWORD dwColor, const wchar_t* txt, DWORD num)
	{
		wchar_t *pwBuffer = (wchar_t*)txt;
		int advanceX = 0;

		if (num == 0)
			num = (DWORD)wcslen(txt);

		while (num > 0)
		{
			wchar_t c = *pwBuffer;
			_WordLocate *pLocate = _wordLocate + c;
			if (pLocate->uTextureIdx == 0xffff)
			{
				HRESULT hr = PaintTexture(c);
				if (hr == E_HANDLE)
				{
					c = _errorChar;
					hr = PaintTexture(c);
				}

				MK_PROCESS_COM_ERROR(hr);
			}

			_WordDimension *pDimension = _wordDimension + c;

			if (_borderType == FBT_BORDER && _borderWeight)
			{
				for (int i = -(int)_borderWeight; i <= (int)_borderWeight; i++)
				{
					for (int j = -(int)_borderWeight; j <= (int)_borderWeight; j++)
					{
						if (i == 0  && j == 0)
							continue;

						DrawChar(c, x + i, y + j, _borderColor, advanceX);
					}
				}
			}
			else if (_borderType == FBT_PROJECTION && _borderWeight)
			{
				for (int i = 0; i <= (int)_borderWeight; i++)
				{
					for (int j = 0; j <= (int)_borderWeight; j++)
					{
						if (i && j)
							DrawChar(c, x + i, y + j, _borderColor, advanceX);
					}
				}

			}

			DrawChar(c, x, y, dwColor, advanceX);

			x += advanceX;

			pwBuffer++;
			num--;
		}

		return TRUE;

Exit0:
		return E_FAIL;
	}

	BOOL CFTFont::GetTextDimension(const wchar_t* txt, DWORD count, float* width, float* height)
	{
		HRESULT hr = E_FAIL;
		*width = 0;
		*height = (float)_points;

		if (count == 0)
			count = (DWORD)wcslen(txt);

		wchar_t *pwBuffer = (wchar_t*)txt;
		while (count > 0)
		{
			wchar_t c = *pwBuffer;
			_WordDimension *pDimension = _wordDimension + c;
			if (pDimension->uWidth == 0xffff)
			{
				HRESULT hr = PaintTexture(c);
				if (hr == E_HANDLE)
				{
					c = _errorChar;
					hr = PaintTexture(c);
				}

				MK_PROCESS_COM_ERROR(hr);
			}

			{
				*width += pDimension->uAdvanceX;
				float h = (float)(pDimension->uHeight + pDimension->OffsetY);
				if (h > *height)
					*height = h;
			}

			pwBuffer++;
			count--;
		}

		return TRUE;
Exit0:
		return FALSE;
	}

	std::string CFTFont::GetResourceName()
	{
		char name[1024];
		sprintf(name, "%s_%d_%d", _fileName.c_str(), _bAntialiased, _points);
		return std::string(name);
	}

	void CFTFont::SetBorderParam(FONT_BORDER_TYPE type, DWORD color, DWORD weight)
	{
		_borderType = type;
		_borderColor = color;
		_borderWeight = weight;
	}

	void CFTFont::SetClipRect(BOOL bEnable, float left, float top, float right, float bottom)
	{
		_enableClip = bEnable;
		_clipLeft = left;
		_clipTop = top;
		_clipRight = right;
		_clipBottom = bottom;
	}
}